/*******************************************************************************
 * Copyright (c) 2012-2014 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/

package org.eclipse.che.jdt.dom;

import org.eclipse.jdt.internal.core.util.Util;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.parser.Scanner;
import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
import org.eclipse.jdt.internal.core.JavaModelStatus;
import org.eclipse.jdt.internal.core.util.Messages;

/**
 * @author Evgen Vidolob
 */
public class JavaConventions {

    private static final String  PACKAGE_INFO = new String(TypeConstants.PACKAGE_INFO_NAME);
    private static final Scanner SCANNER      =
            new Scanner(false /*comment*/, true /*whitespace*/, false /*nls*/, ClassFileConstants.JDK1_3 /*sourceLevel*/, null/*taskTag*/,
                        null/*taskPriorities*/, true /*taskCaseSensitive*/);

    /**
     * Validate the given compilation unit name for the given source and compliance levels.
     * <p>
     * A compilation unit name must obey the following rules:
     * <ul>
     * <li> it must not be null
     * <li> it must be suffixed by a dot ('.') followed by one of the
     *       {@link org.eclipse.jdt.core.JavaCore#getJavaLikeExtensions() Java-like extensions}
     * <li> its prefix must be a valid identifier
     * <li> it must not contain any characters or substrings that are not valid
     *		   on the file system on which workspace root is located.
     * </ul>
     * </p>
     * @param name the name of a compilation unit
     * @param sourceLevel the source level
     * @param complianceLevel the compliance level
     * @return a status object with code <code>IStatus.OK</code> if
     *		the given name is valid as a compilation unit name, otherwise a status
     *		object indicating what is wrong with the name
     * @since 3.3
     */
    public static IStatus validateCompilationUnitName(String name, String sourceLevel, String complianceLevel) {
        if (name == null) {
            return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.convention_unit_nullName, null);
        }
        if (!Util.isJavaLikeFileName(name)) {
            return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.convention_unit_notJavaName, null);
        }
        String identifier;
        int index;
        index = name.lastIndexOf('.');
        if (index == -1) {
            return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.convention_unit_notJavaName, null);
        }
        identifier = name.substring(0, index);
        // JSR-175 metadata strongly recommends "package-info.java" as the
        // file in which to store package annotations and
        // the package-level spec (replaces package.html)
        if (!identifier.equals(PACKAGE_INFO)) {
            IStatus status = validateIdentifier(identifier, sourceLevel, complianceLevel);
            if (!status.isOK()) {
                return status;
            }
        }
//        IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
//        if (!status.isOK()) {
//            return status;
//        }
        return JavaModelStatus.VERIFIED_OK;
    }

    /**
     * Validate the given .class file name for the given source and compliance levels.
     * <p>
     * A .class file name must obey the following rules:
     * <ul>
     * <li> it must not be null
     * <li> it must include the <code>".class"</code> suffix
     * <li> its prefix must be a valid identifier
     * <li> it must not contain any characters or substrings that are not valid
     *		   on the file system on which workspace root is located.
     * </ul>
     * </p>
     * @param name the name of a .class file
     * @param sourceLevel the source level
     * @param complianceLevel the compliance level
     * @return a status object with code <code>IStatus.OK</code> if
     *		the given name is valid as a .class file name, otherwise a status
     *		object indicating what is wrong with the name
     * @since 3.3
     */
    public static IStatus validateClassFileName(String name, String sourceLevel, String complianceLevel) {
        if (name == null) {
            return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.convention_classFile_nullName, null);		}
        if (!org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) {
            return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.convention_classFile_notClassFileName, null);
        }
        String identifier;
        int index;
        index = name.lastIndexOf('.');
        if (index == -1) {
            return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.convention_classFile_notClassFileName, null);
        }
        identifier = name.substring(0, index);
        // JSR-175 metadata strongly recommends "package-info.java" as the
        // file in which to store package annotations and
        // the package-level spec (replaces package.html)
        if (!identifier.equals(PACKAGE_INFO)) {
            IStatus status = validateIdentifier(identifier, sourceLevel, complianceLevel);
            if (!status.isOK()) {
                return status;
            }
        }
//        IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
//        if (!status.isOK()) {
//            return status;
//        }
        return JavaModelStatus.VERIFIED_OK;
    }


    /**
     * Validate the given Java identifier for the given source and compliance levels
     * The identifier must not have the same spelling as a Java keyword,
     * boolean literal (<code>"true"</code>, <code>"false"</code>), or null literal (<code>"null"</code>).
     * See section 3.8 of the <em>Java Language Specification, Second Edition</em> (JLS2).
     * A valid identifier can act as a simple type name, method name or field name.
     *
     * @param id the Java identifier
     * @param sourceLevel the source level
     * @param complianceLevel the compliance level
     * @return a status object with code <code>IStatus.OK</code> if
     *		the given identifier is a valid Java identifier, otherwise a status
     *		object indicating what is wrong with the identifier
     * @since 3.3
     */
    public static IStatus validateIdentifier(String id, String sourceLevel, String complianceLevel) {
        if (scannedIdentifier(id, sourceLevel, complianceLevel) != null) {
            return JavaModelStatus.VERIFIED_OK;
        } else {
            return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.bind(Messages.convention_illegalIdentifier, id), null);
        }
    }

    /*
 * Returns the current identifier extracted by the scanner (without unicode
 * escapes) from the given id and for the given source and compliance levels.
 * Returns <code>null</code> if the id was not valid
 */
    private static synchronized char[] scannedIdentifier(String id, String sourceLevel, String complianceLevel) {
        if (id == null) {
            return null;
        }
        // Set scanner for given source and compliance levels
        SCANNER.sourceLevel = sourceLevel == null ? ClassFileConstants.JDK1_3 : CompilerOptions.versionToJdkLevel(sourceLevel);
        SCANNER.complianceLevel = complianceLevel == null ? ClassFileConstants.JDK1_3 : CompilerOptions.versionToJdkLevel(complianceLevel);

        try {
            SCANNER.setSource(id.toCharArray());
            int token = SCANNER.scanIdentifier();
            if (token != TerminalTokens.TokenNameIdentifier) return null;
            if (SCANNER.currentPosition == SCANNER.eofPosition) { // to handle case where we had an ArrayIndexOutOfBoundsException
                try {
                    return SCANNER.getCurrentIdentifierSource();
                } catch (ArrayIndexOutOfBoundsException e) {
                    return null;
                }
            } else {
                return null;
            }
        }
        catch (InvalidInputException e) {
            return null;
        }
    }

}
